#!/bin/bash
#
# Descr: Скрипт получения информации об узлах из конф. файла
#
# Данный скрипт используется только в скрипте syncctl и выделен
# в отдельный модуль только для того, чтобы не "засорять"
# пространство имен главной программы переменными из
# конфигурационного файла узлов.
#
# -------------------------------------------------------------------
# Описание формата конфигурационного файла
# ----------------------------------------
#
# В файле индивидуально для узла или для группы узлов прописываются
# параметры, влияющие на процесс генерации данных:
#
#   1) набор таблиц, данные которых будут пересылаться на узел;
#   2) индивидуально для каждой таблицы:
#         а) вертикальная фильтрация - набор полей таблицы;
#         б) горизонтальная фильтрация - условия, налагаемые
#            на каждую запись таблицы.
#
# Файл разбит на секции двух типов:
#   * необязательная секция для описания групп узлов, и
#   * одна или более секций для описания параметров
#     узлов.
#
# Секция начинается с заголовка секции и заканчивается
# обязательным признаком конца секции.
#
# Признак конца секции должен присутствовать, даже если
# в файле определяется только одна секция или секция
# располагается в конце файла.
#
#
# Секция "Группы узлов"
# =====================
#
# Секция содержит описание одной или более групп узлов.
# Файл может содержать только одну секцию данного типа.
#
# Заголовок секции:     # === Groups ===
# Признак конца секции: # ==============
#
# Формат для определения группы:
#
#   group_<название группы>="<узел|группа>[ <узел|группа>...]"
#
# Группы могут быть вложенными.
#
# ВНИМАНИЕ! Название группы не должно быть частью названий узлов.
# Например, группа azs и узлы azs1, azs2 и т.д. Смените в таком
# случае название группы, напрмер, на azses.
#
#
# Секция "Параметры узлов"
# ========================
#
# Секция содержит описание параметров для узлов, указанных
# в заголовке секции. Файл может содержать несколько секций
# для одного узла.
# 
# Заголовок секции:     # === For nodes: <узел|группа>[ <узел|группа>...]
# Признак конца секции: # ==============
#
#
# Формат для определения таблиц и их полей:
# -----------------------------------------
#
#   table_<владелец>_<таблица>='*[ <вирт.поле1>...]'
#
#       - все поля таблицы плюс необязательные "виртуальные" поля
#
#     или
#
#   table_<владелец>_<таблица>='<поле1>[ <поле2>...]'
#
#       - указанные поля (в том числе "виртуальные") плюс поля,
#         входящие в первичный ключ
#
#   Формат определения "виртуального" поля: <имя>[<тип>]=<значение>
#     где <имя>      - название поля,
#         <тип>      - SQL-тип поля,
#         <значение> - любое допустимое SQL-выражение с одним исключением:
#                      выражение не должно содержать пробелов! 
#
#   Виртуальное поле может "перекрывать" собой любое поле в таблице, в том
#   числе входящее в первичный ключ.
#
#
# Формат для определения дополнительных условий по выборке
# данных из таблиц:
# -----------------
#
#   condi_<владелец>_<таблица>="<условие>"
#
# В условии и в списке полей могут использоваться специальные
# макропеременные, значения которых подставляются во время
# выполнения команды syncctl data prepare:
#
#   <sender_name>     - имя узла, который готовит пакет
#   <receiver_name>   - имя узла, для которого готовится пакет
#   <sender_number>   - число, входящее в <sender_name> (например, для
#                       узла azs99 <sender_number> заменяется на 99)
#   <receiver_number> - число, входящее в <receiver_name> (например, для
#                       узла azs99 <receiver_number> заменяется на 99)
#
# Определение представлений (view)
# --------------------------------
#
# Для включения представления в процесс синхронизации необходимо:
#
#  1) включить каждую таблицу, входящую в представление в процесс
#     синхронизации командой 'syncctl table add <владелец>.<таблица>'
#
#  2) переопределить представление таким образом, чтобы в него вошли
#     все поля Packet от таблиц, входящих в представление:
#
#       create view as select <поля>,
#                             <таблица1>.Packet as pkt_<таблица1>,
#                             <таблица2>.Packet as pkt_<таблица2>,
#                             ...
#                        from <таблица1>,
#                             <таблица2>,
#                             ...
#                       where <условие>
#
#  3) добавить представление в файл nodes.conf как обычную таблицу
#     с двумя исключениями:
#       - в списке полей нельзя использовать знак '*' (все поля);
#       - в списке полей обязательно должен быть определен список
#         полей, входящих в "виртуальный" первичный ключ предста-
#         вления, путем заключения этих полей в фигурные скобки {}
#
#     table_<владелец>_<представление>='{<ключ.поле1> <ключ.поле2>...}[ <поля>]'
#     condi_<владелец>_<представление>="<условие>"
#
# Переименование таблиц и представлений
# -------------------------------------
#
#   alias_<владелец>_<таблица>="<НОВЫЙ владелец>.<НОВОЕ название таблицы>"
#
#
USAGE()
{
echo "$1 - Получить информацию по узлу или группе узлов из конфигурационного
     файла узлов

$1 <узел/группа> <объект> [for <таблица>]

Объекты:

  nodelist      - список узлов, входящих в группу. Если в качестве
                  первого параметра задан узел, то он и будет
                  возращен
  grouplist     - список групп, в которые входит узел
  tablelist     - список таблиц, данные которых передаются на узел
  fieldlist     - список полей таблицы
  condition     - условие для выборки данных из таблицы 
  tablealias    - другое имя таблицы, если оно определено. Иначе
                  это же имя.
  config        - текущая конфигурация узла

Примечание:
  Скрипт использует файл настройки sync.conf"
}

if [ $# -lt 2 ]
then
	USAGE $0
	exit 1
fi

# ********************************************************************
# Global settings

. `dirname $0`/etc/common
. `dirname $0`/etc/sync.conf

# ********************************************************************
# Local settings

SCRIPT_NAME=`basename $0`	# Имя скрипта. Используется для
				# отображения имени скрипта в
				# сообщениях лога 

MAX_GROUP_LEVEL=10		# Максимальный уровень вложенности групп
PROPERTY_PREFIX=nodeconf_	# Префикс в именах параметров конф.
				# файла, когда они используются в
				# в качестве переменных окружения

# Списки объектов и действий над ними, допустимые для
# указания в командной строке

list_object="nodelist grouplist tablelist fieldlist condition tablealias config"

#

TYPE_NODES=n
TYPE_GROUPS=g

#

SECTION_GROUP_START="^# === Groups ==="
SECTION_GROUP_STOP="^# =============="

SECTION_NODE_START="^# === For nodes:.* (<node>[^a-zA-Z0-9].*|<node>$)"
SECTION_NODE_STOP="^# =============="

# ********************************************************************
# main - Главная процедура, с которой начинается выполнение скрипта
# ----
# Проверяет правильность задания аргументов скрипта и вызывает
# соответствующую им процедуру.
# 
# Параметры:
#   node    - имя узла
#   object  - имя объекта 
#   options - дополнительные опции

main()
{
  typeset object options

  node=$1
  object=$2
  shift 2
  options=$*

  # Проверить правильность задания объекта в командной строке

  if inlist "$object" "$list_object"
  then
    :
  else
    logmsg $ST_ERR 1 1 $0 "Неизвестный тип объекта \"$object\""
    return 1
  fi

  # Проверить, существует ли конфигурационный файл узлов

  if [ ! -f $PATH_ETCS/$FILENAME_NODES_CONF ]
  then
    logmsg $ST_ERR 1 1 $0 "Конфигурационный файл узлов \"$PATH_ETCS/$FILENAME_NODES_CONF\" не существует"
    return 1
  fi

  # Вызвать процедуру обработки заданной команды

  cmd="cmd_${object}"

  logmsg $ST_INFO 3 0 $0 "Запуск команды \"$object $options\""
  eval $cmd $node $options
}

# ********************************************************************
# cmd_nodelist - Процедура исполнения команды nodelist
# ------------
# Параметры:
#   group - название группы или имя удаленного узла

cmd_nodelist()
{
  typeset group

  group=$1
  get_extlist $TYPE_NODES $group
  return $?
}

# ********************************************************************
# cmd_grouplist - Процедура исполнения команды grouplist
# -------------
# Параметры:
#   node - имя удаленного узла

cmd_grouplist()
{
  typeset node

  node=$1
  get_extlist $TYPE_GROUPS $node
  return $?
}

# ********************************************************************
# get_extlist - Получить полный список объектов, входящих в заданный
#               начальный список
# -----------
# Параметры:
#   type      - список каких объектов возвращать:
#                 nodes  -
#                 groups - 
#   items     - начальный список

get_extlist()
{
  typeset type item

  type="$1"
  item="$2"

  load_group_section
  grouplist="`set | sed -n "/^${PROPERTY_PREFIX}group_\(.*\)=.*/s//\1/p"`"

  if [ "$type" = "$TYPE_NODES" ]
  then
    list="$(get_nodelist $item "$grouplist" 0)"
  else
    list="$(get_grouplist $item "$grouplist" 0)"
  fi

  if [ -n "$list" ]
  then
    echo "$list" | sort -u
  fi

  return 0
}

# ********************************************************************
# get_nodelist - Получить полный список узлов, входящих в заданный
#                список узлов
# ------------
# Параметры:
#   items     - начальный список
#   grouplist - список всех групп, определенных в конф. файле
#   level     - текущий уровень рекурсии

get_nodelist()
{
  typeset items grouplist
  typeset -i level
  typeset items2

  items="$1"
  grouplist="$2"
  let level=$3

  if [ $level -gt $MAX_GROUP_LEVEL ]
  then
    logmsg $ST_ERR 1 0 $0 "Превышен максимальный уровень вложенности групп"
    return 1
  fi

  for item in $items
  do
    if inlist $item "$grouplist"
    then
      let level=level+1
      items2=$(value_by_name ${PROPERTY_PREFIX}group_${item})
      get_nodelist "$items2" "$grouplist" $level
      if [ $? -ne 0 ]; then return 1; fi
    else
      echo $item
    fi
  done
  return 0
}

# ********************************************************************
# get_grouplist - Получить список групп, в которые входит заданный
#                 элемент
# -------------
# Параметры:
#   item      - элемент списка (группа или узел)
#   grouplist - список всех групп, определенных в конф. файле
#   level     - текущий уровень рекурсии

get_grouplist()
{
  typeset item grouplist
  typeset -i level

  item="$1"
  grouplist="$2"
  let level=$3

  if [ $level -gt $MAX_GROUP_LEVEL ]
  then
    logmsg $ST_ERR 1 0 $0 "Превышен максимальный уровень вложенности групп"
    return 1
  fi

  for group in $grouplist
  do
    if inlist $item "$(value_by_name ${PROPERTY_PREFIX}group_${group})"
    then
      echo $group
      let level=level+1
      get_grouplist "$group" "$grouplist" $level
      if [ $? -ne 0 ]; then return 1; fi
    fi
  done
  return 0
}

# ********************************************************************
# cmd_tablelist - Процедура исполнения команды tablelist
# -------------
# Параметры:
#   node - имя удаленного узла

cmd_tablelist()
{
  typeset node
  typeset section ordered tables str

  node=$1

  # Получить список таблиц в том порядке, в котором они описаны
  # в конфигурационном файле 

  section="$(read_node_section $node)"
  ordered="`echo "$section" | sed -n "/^TABLE_\([^_]*\)_\([^=]*\)=.*$/s//\1.\2/p"`"

  # Убрать из полученного списка дублирующие строки

  tables=""
  for str in $ordered
  do
    if inlist "$str" "$tables"
    then
      :
    else
      tables="$tables $str"
    fi
  done

  # Получить список таблиц в виде набора строк

  for str in $tables
  do
    echo $str
  done
}

# ********************************************************************
# cmd_fieldlist - Процедура исполнения команды fieldlist
# -------------
# Параметры:
#   node      - имя удаленного узла
#   "for"     - служебное слово
#   tablename - полное имя таблицы

cmd_fieldlist()
{
  typeset node tablename

  node=$1
  tablename=$3

  get_table_property $node "table" $tablename
}

# ********************************************************************
# cmd_condition - Процедура исполнения команды condition
# -------------
# Параметры:
#   node      - имя удаленного узла
#   "for"     - служебное слово
#   tablename - полное имя таблицы

cmd_condition()
{
  typeset node tablename

  node=$1
  tablename=$3

  get_table_property $node "condi" $tablename
}

# ********************************************************************
# cmd_tablealias - Процедура исполнения tablealias
# --------------
# Параметры:
#   node      - имя удаленного узла
#   "for"     - служебное слово
#   tablename - полное имя таблицы

cmd_tablealias()
{
  typeset node tablename

  node=$1
  tablename=$3

  alias=$(get_table_property $node "alias" $tablename)
  if [ -z "$alias" ]
  then
    alias=$tablename
  fi

  echo $alias
}

# ********************************************************************
# cmd_config - Процедура исполнения команды config
# ----------
# Параметры:
#   node - имя удаленного узла

cmd_config()
{
  typeset node
  typeset section

  node=$1

  # Получить конфигурацию узла

  section="$(read_node_section $node)"
  section="`echo "$section" | sed -n "/^\([^ ]*\)=\(.*\)/s//\1=\2/p"`"
  echo "$section"
}

# ********************************************************************
# load_group_section - Загрузить информацию о группах из
#                      конфигурационного файла узлов в виде
#                      переменных среды, причем в имена
#                      параметров добавляется префикс
# ------------------
# Параметры:

load_group_section()
{
  typeset section

  sec_start="$SECTION_GROUP_START"
  sec_stop="$SECTION_GROUP_STOP"

  section="`cat $PATH_ETCS/$FILENAME_NODES_CONF | awk "/$sec_start/, /$sec_stop/"`"
  section="`echo "$section" | sed "/^\([^ \#]*\)=\(.*\)/s//$PROPERTY_PREFIX\1=\2/"`"
  eval "$section"
}

# ********************************************************************
# read_node_section - Загрузить параметры секций, относящихся к узлу
#                     из конфигурационного файла, причем содержимое
#                     секции приводится к верхнему регистру
# -----------------
# Параметры:
#   node - имя удаленного узла

read_node_section()
{
  typeset node
  typeset group
  typeset sec_start sec_stop section

  node=$1

  sec_start="/$(replace "$SECTION_NODE_START" "<node>" $node)/"
  sec_stop="$SECTION_NODE_STOP"

  for group in $(cmd_grouplist $node)
  do
    sec_start="$sec_start || /$(replace "$SECTION_NODE_START" "<node>" $group)/"
  done

  section="$(sed -e :a -e '/\\$/N; s/\\\n//; ta' $PATH_ETCS/$FILENAME_NODES_CONF | awk "$sec_start, /$sec_stop/")"
  section="`echo "$section" | awk "$sec_start, /$sec_stop/"`"
  section="$(echo "$section" | sed 's/#.*$//;/^$/d')"

  section="$(upper "$section")"
  echo "$section"
}

# ********************************************************************
# load_node_section - Загрузить параметры секций, относящихся к узлу
#                     в виде переменных среды, причем в имена 
#                     параметров добавляется префикс 
# -----------------
# Параметры:
#   section - секция

load_node_section()
{
  typeset section

  section="$1"

  section="`echo "$section" | sed "/^\([^ ]*\)=\(.*\)/s//$PROPERTY_PREFIX\1=\2/"`"
  eval "$section"
}

# ********************************************************************
# get_table_property - Вернуть
# ------------------
# Параметры:
#   node      - имя удаленного узла
#   property  - имя свойства
#   tablename - полное имя таблицы

get_table_property()
{
  typeset node property tablename owner table

  node=$1
  property=$(upper $2)
  tablename=$(upper $3)

  owner=$(table_owner $tablename)
  table=$(table_name $tablename)

  if [ -z "$owner" -o -z "$table" ]
  then
    logmsg $ST_ERR 1 1 $0 "Неправильно указана таблица. Правильный формат: <владелец>.<таблица>"
    return 1
  fi

  section="$(read_node_section $node)"
  load_node_section "$section"
  echo "$(value_by_name "${PROPERTY_PREFIX}${property}_${owner}_${table}")"
}

# ********************************************************************

logmsg $ST_INFO 1 0 "" "Начало выполнения команды \"`basename $0` $*\"..."

main $*

if [ $? -eq 0 ]
then
  logmsg $ST_INFO 1 0 "" "Команда \"`basename $0` $*\" успешно выполнена"
  exit 0
else
  logmsg $ST_INFO 1 0 "" "Команда \"`basename $0` $*\" выполнилась с ошибкой"
  exit 1
fi
